package com.betomaluje.miband; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Context; import android.content.Intent; import android.util.Log; import com.betomaluje.miband.bluetooth.BLEAction; import com.betomaluje.miband.bluetooth.BLETask; import com.betomaluje.miband.bluetooth.BTCommandManager; import com.betomaluje.miband.bluetooth.BTConnectionManager; import com.betomaluje.miband.bluetooth.MiBandWrapper; import com.betomaluje.miband.bluetooth.NotificationConstants; import com.betomaluje.miband.bluetooth.WaitAction; import com.betomaluje.miband.bluetooth.WriteAction; import com.betomaluje.miband.colorpicker.ColorPickerDialog; import com.betomaluje.miband.model.BatteryInfo; import com.betomaluje.miband.model.LedColor; import com.betomaluje.miband.model.Profile; import com.betomaluje.miband.model.Protocol; import com.betomaluje.miband.model.UserInfo; import com.betomaluje.miband.model.VibrationMode; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; public class MiBand { private static final String TAG = "miband-android"; private static Context context; private static BTCommandManager io; private static MiBand instance; private static MiBandWrapper miBandWrapper; private static Intent miBandService; private static BTConnectionManager btConnectionManager; private ActionCallback connectionCallback; public MiBand(final Context context) { MiBand.context = context; MiBand.miBandWrapper = MiBandWrapper.getInstance(context); ActionCallback myConnectionCallback = new ActionCallback() { @Override public void onSuccess(Object data) { Log.d(TAG, "Connection success, now pair: " + data); //only once we are paired, we create the BluetoothIO object to communicate with Mi Band io = new BTCommandManager(context, btConnectionManager.getGatt()); btConnectionManager.setIo(io); setUserInfo(UserInfo.getSavedUser(context)); if (connectionCallback != null) connectionCallback.onSuccess(null); } @Override public void onFail(int errorCode, String msg) { Log.e(TAG, "Fail: " + msg); if (connectionCallback != null) connectionCallback.onFail(errorCode, msg); } }; MiBand.btConnectionManager = BTConnectionManager.getInstance(context, myConnectionCallback); } public synchronized static MiBand getInstance(Context context) { if (instance == null) { instance = new MiBand(context); } else { MiBand.context = context; } return instance; } public static void initService(Context context) { miBandService = new Intent(context, MiBandService.class); miBandService.setAction(NotificationConstants.MI_BAND_CONNECT); context.startService(miBandService); } public static void sendAction(final int action) { if (miBandWrapper == null) return; miBandWrapper.sendAction(action); } public static void sendAction(final int action, HashMap<String, ? extends Object> params) { if (miBandWrapper == null) return; miBandWrapper.sendAction(action, params); } public static void disconnect() { Log.e(TAG, "Disconnecting Mi Band..."); if (miBandService != null) MiBand.context.stopService(miBandService); btConnectionManager.disconnect(); } public static void dispose() { Log.e(TAG, "Disposing Mi Band..."); if (miBandService != null) MiBand.context.stopService(miBandService); btConnectionManager.dispose(); } /** * Android device will automatically search for nearby Mi Band, automatic connection, because the hand will have only one Mi Band, * currently only supports the search to case a bracelet * * @param callback */ public void connect(final ActionCallback callback) { if (!isConnected()) { connectionCallback = callback; btConnectionManager.connect(); } else { Log.e(TAG, "Already connected..."); } } private void checkConnection() { if (!isConnected()) { Log.e(TAG, "Not connected... Waiting for new connection..."); btConnectionManager.connect(); } } /** * Checks if the connection is already done with the Mi Band * * @return if the Mi Band is connected */ public boolean isConnected() { return btConnectionManager.isConnected(); } /** * Pairs with Mi Band, for practical purposes unknown, mismatch can also do other operation. */ public void pair() { Log.d(TAG, "Pairing..."); ActionCallback ioCallback = new ActionCallback() { @Override public void onSuccess(Object data) { BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic) data; //Log.d(TAG, "pair result " + Arrays.toString(characteristic.getValue())); if (characteristic.getValue().length == 1 && characteristic.getValue()[0] == 2) { Log.d(TAG, "Pairing success!"); setUserInfo(UserInfo.getSavedUser(context)); //setUserInfo(null); if (connectionCallback != null) connectionCallback.onSuccess(null); } else { if (connectionCallback != null) connectionCallback.onFail(-1, "failed to pair with Mi Band"); } } @Override public void onFail(int errorCode, String msg) { if (connectionCallback != null) connectionCallback.onFail(errorCode, msg); } }; MiBand.io.writeAndRead(Profile.UUID_CHAR_PAIR, Protocol.PAIR, ioCallback); } /** * Signal strength reading and the connected device RSSI value * * @param callback */ public void readRssi(ActionCallback callback) { checkConnection(); MiBand.io.readRssi(callback); } /** * Read band battery information */ public void getBatteryInfo(final ActionCallback callback) { checkConnection(); ActionCallback ioCallback = new ActionCallback() { @Override public void onSuccess(Object data) { BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic) data; Log.d(TAG, "getBatteryInfo result " + Arrays.toString(characteristic.getValue())); if (characteristic.getValue().length == 10) { BatteryInfo info = BatteryInfo.fromByteData(characteristic.getValue()); callback.onSuccess(info); } else { callback.onFail(-1, "result format wrong!"); } } @Override public void onFail(int errorCode, String msg) { callback.onFail(errorCode, msg); } }; MiBand.io.readCharacteristic(Profile.UUID_CHAR_BATTERY, ioCallback); } /** * Let band vibrate */ public void startVibration(VibrationMode mode) { checkConnection(); //TODO see if new firmware or not byte[] protocal; switch (mode) { case VIBRATION_WITH_LED: protocal = Protocol.VIBRATION_WITH_LED; break; case VIBRATION_UNTIL_CALL_STOP: protocal = Protocol.VIBRATION_UNTIL_CALL_STOP; break; case VIBRATION_WITHOUT_LED: protocal = Protocol.VIBRATION_WITHOUT_LED; break; case VIBRATION_NEW_FIRMWARE: protocal = Protocol.VIBRATION_NEW_FIRMWARE; break; default: return; } final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_SERVICE_VIBRATE, Profile.UUID_CHAR_ALERT_LEVEL, protocal)); queue(list); } /** * Vibrate "times" times. Each iteration will start vibrator "on_time" milliseconds (up to 500, will be truncated if larger), and then stop it "off_time" milliseconds (no limit here). * * @param times : the amount of times to vibrate * @param onTime : the time in milliseconds that each vibration will last (maximum of 500 milliseconds). Preferably more than 100 milliseconds * @param offTime : the time in milliseconds that each cycle will last */ public synchronized void customVibration(final int times, final int onTime, final int offTime) { final int newOnTime = Math.min(onTime, 500); List<BLEAction> list = new ArrayList<>(); for (int i = 1; i <= times; i++) { list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.VIBRATION_UNTIL_CALL_STOP)); list.add(new WaitAction(newOnTime)); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.STOP_VIBRATION)); list.add(new WaitAction(offTime)); } queue(list); } /** * Stops a vibration */ public void stopVibration() { checkConnection(); //MiBand.io.writeCharacteristic(Profile.UUID_CHAR_CONTROL_POINT, Protocol.STOP_VIBRATION, null); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.STOP_VIBRATION)); queue(list); } public void setNormalNotifyListener(NotifyListener listener) { MiBand.io.setNotifyListener(Profile.UUID_CHAR_NOTIFICATION, listener); } /** * Sets the listener for steps in real time. Use {@link MiBand#enableRealtimeStepsNotify} to start it and {@link MiBand#disableRealtimeStepsNotify} to stop it. * * @param listener */ public void setRealtimeStepsNotifyListener(final RealtimeStepsNotifyListener listener) { checkConnection(); MiBand.io.setNotifyListener(Profile.UUID_CHAR_REALTIME_STEPS, new NotifyListener() { @Override public void onNotify(byte[] data) { Log.d(TAG, Arrays.toString(data)); if (data.length == 4) { int steps = data[3] << 24 | (data[2] & 0xFF) << 16 | (data[1] & 0xFF) << 8 | (data[0] & 0xFF); listener.onNotify(steps); } } }); } /** * Starts listening to step count in real time */ public void enableRealtimeStepsNotify() { checkConnection(); //MiBand.io.writeCharacteristic(Profile.UUID_CHAR_CONTROL_POINT, Protocol.ENABLE_REALTIME_STEPS_NOTIFY, null); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.ENABLE_REALTIME_STEPS_NOTIFY)); final BLETask task = new BLETask(list); queue(list); } /** * Stops listening to step count in real time */ public void disableRealtimeStepsNotify() { checkConnection(); //MiBand.io.writeCharacteristic(Profile.UUID_CHAR_CONTROL_POINT, Protocol.DISABLE_REALTIME_STEPS_NOTIFY, null); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.DISABLE_REALTIME_STEPS_NOTIFY)); queue(list); } /** * Sets the led light color. Flashes the LED's by default * * @param color : the given {@link LedColor} color */ public void setLedColor(LedColor color) { setLedColor(color, true); } /** * Sets the led light color. * * @param color : the given {@link LedColor} color * @param quickFlash : <b>true</b> if you want the band's LED's to flash, <b>false</b> otherwise */ public void setLedColor(LedColor color, boolean quickFlash) { byte[] protocal; switch (color) { case RED: protocal = Protocol.COLOR_RED; break; case BLUE: protocal = Protocol.COLOR_BLUE; break; case GREEN: protocal = Protocol.COLOR_GREEN; break; case ORANGE: protocal = Protocol.COLOR_ORANGE; break; case TEST: protocal = Protocol.COLOR_TEST; break; default: return; } protocal[protocal.length - 1] = quickFlash ? (byte) 1 : (byte) 0; setColor(protocal); } /** * Sets the LED color. Flashes the LED's by default * * @param rgb : an <b>int</b> that represents the rgb value (use {@link ColorPickerDialog} to select a value) */ public void setLedColor(int rgb) { setLedColor(rgb, true); } /** * Sets the LED color. * * @param rgb : an <b>int</b> that represents the rgb value (use {@link ColorPickerDialog} to select a value) * @param quickFlash : <b>true</b> if you want the band's LED's to flash, <b>false</b> otherwise */ public void setLedColor(int rgb, boolean quickFlash) { byte[] colors = convertRgb(rgb, quickFlash); setColor(colors); } /** * Actually sends the color to the Mi Band * * @param color */ private void setColor(byte[] color) { checkConnection(); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, color)); queue(list); } private byte[] convertRgb(int rgb) { return convertRgb(rgb, true); } private byte[] convertRgb(int rgb, boolean quickFlash) { final int red = ((rgb >> 16) & 0x0ff) / 42; final int green = ((rgb >> 8) & 0x0ff) / 42; final int blue = ((rgb) & 0x0ff) / 42; return new byte[]{14, (byte) red, (byte) green, (byte) blue, quickFlash ? (byte) 1 : (byte) 0}; } /** * Sends a custom notification to the Mi Band */ public synchronized void setLedColor(final int flashTimes, final int flashColour, final int flashDuration) { final List<BLEAction> list = new ArrayList<>(); byte[] colors = convertRgb(flashColour); byte[] protocalOff = {14, colors[0], colors[1], colors[2], 0}; for (int i = 1; i <= flashTimes; i++) { list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, colors)); list.add(new WaitAction(flashDuration)); list.add((new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, protocalOff))); list.add(new WaitAction(flashDuration)); } queue(list); } public synchronized void notifyBand(final int flashColour) { List<BLEAction> list = new ArrayList<>(); byte[] colors = convertRgb(flashColour); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.VIBRATION_WITHOUT_LED)); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, colors)); queue(list); } private void queue(List<BLEAction> list) { final BLETask task = new BLETask(list); try { io.queueTask(task); } catch (NullPointerException ignored) { } } /** * Notifies the Mi Band with vibration and colour. * Vibrate and flashes the colour "times" times. Each iteration will start "on_time" milliseconds (up to 500, will be truncated if larger), and then stop it "off_time" milliseconds (no limit here). * * @param times : the amount of times to vibrate * @param onTime : the time in milliseconds that each vibration will last (maximum of 500 milliseconds). Preferably more than 100 milliseconds * @param offTime : the time in milliseconds that each cycle will last * @param flashColour int value of the colour to flash */ public synchronized void notifyBand(final int times, final int onTime, final int offTime, final int flashColour) { //final int newOnTime = Math.min(onTime, 500); final List<BLEAction> list = new ArrayList<>(); byte[] colors = convertRgb(flashColour); list.add(new WaitAction(150)); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.VIBRATION_WITHOUT_LED)); list.add(new WaitAction(300)); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, colors)); /* for (int i = 1; i <= times; i++) { //vibration part list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.VIBRATION_WITHOUT_LED)); //list.add(new WaitAction(newOnTime)); //list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.STOP_VIBRATION)); list.add(new WaitAction(offTime)); } */ queue(list); } /** * Sets up the user information. If there's no UserInfo provided, we create one by default * * @param userInfo */ public void setUserInfo(UserInfo userInfo) { checkConnection(); BluetoothDevice device = btConnectionManager.getDevice(); if (userInfo == null) { userInfo = UserInfo.getDefault(device.getAddress(), context); } final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_USER_INFO, userInfo.getData())); queue(list); } public void setUserInfo(int gender, int age, int height, int weight, String alias) { UserInfo user = UserInfo.create(btConnectionManager.getDevice().getAddress(), gender, age, height, weight, alias, 0); //MiBand.io.writeCharacteristic(Profile.UUID_CHAR_USER_INFO, user.getData(), null); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_USER_INFO, user.getData())); queue(list); } /** * Your Mi Band will do crazy things (LED flashing, vibrate). * Note: This will remove bonding information on the Mi Band, which might confused Android. * So before you connect next time remove your Mi Band via Settings, Bluetooth. */ public void selfTest() { checkConnection(); //MiBand.io.writeCharacteristic(Profile.UUID_CHAR_TEST, Protocol.SELF_TEST, null); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_TEST, Protocol.SELF_TEST)); queue(list); } public void startListeningSync(ActionCallback actionCallback) { btConnectionManager.toggleNotifications(true); final List<BLEAction> list = new ArrayList<>(); list.add(new WriteAction(Profile.UUID_CHAR_CONTROL_POINT, Protocol.FETCH_DATA, actionCallback)); queue(list); } public void stopListeningSync() { btConnectionManager.toggleNotifications(false); } public boolean isSyncNotification() { return btConnectionManager.isSyncNotification(); } }